package zyf.jmidifile;

import androidx.annotation.NonNull;

import java.io.Serializable;
import java.util.Arrays;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;

public class JMidiPlayer implements Serializable {
    private JMidiFile midiFile;
    public final long duration;
    private long passedTime;
    private int state;
    private Timer timer;
    private long lastScheduleTime;
    private int[] cursors;
    private HashSet<EventListener> eventListeners = new HashSet<>();
    private JMidiEvent[] notesResumeCache;

    public static final int STATE_STOPPED = 0;
    public static final int STATE_PLAYING = 1;
    public static final int STATE_PAUSED = 2;

    private static final int SCHEDULE_PERIOD = 10;
    private static final int PITCH_LIMIT = 128;

    // 该接口需要注意调用可能发生在非主线程
    public interface EventListener {
        // 播放器状态发生改变
        void onStateChange(JMidiPlayer player, int oldState, int newState);

        // midi events 分发
        void onEvents(JMidiPlayer player, int tid, List<JMidiEvent> events, boolean isResumedNotes);

        /**
         * 播放器内部定时器每次更新时间会调用这个接口
         *
         * @param deltaMs 相距上一次调用 update 过去了多久
         */
        void onUpdate(JMidiPlayer player, long deltaMs);
    }

    /**
     * @param isResumeNote 控制是否在 play 接口调用的时候，重放那些没有播完的 note on 事件
     */
    public JMidiPlayer(@NonNull JMidiFile midiFile, boolean isResumeNote) {
        this.midiFile = midiFile;
        this.duration = midiFile.getDuration(-1);
        this.state = STATE_STOPPED;
        cursors = new int[midiFile.getTrackCount()];
        if (isResumeNote) {
            notesResumeCache = new JMidiEvent[cursors.length * PITCH_LIMIT];
            Arrays.fill(notesResumeCache, null);
        }
    }

    public synchronized void play() {
        if (state == STATE_PLAYING) return;
        int oldState = state;
        // 重置 cursor
        if (state == STATE_STOPPED) {
            passedTime = 0;
            for (int i = 0; i < cursors.length; ++i) {
                cursors[i] = 0;
            }
        }
        resumeNotes();
        lastScheduleTime = System.currentTimeMillis();
        state = STATE_PLAYING;
        this.timer = new Timer();
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                update(System.currentTimeMillis());
            }
        }, 0, SCHEDULE_PERIOD);
        onStateChange(oldState, state);
    }

    public synchronized void pause() {
        if (state != STATE_PLAYING) return;
        int oldState = state;
        state = STATE_PAUSED;
        cancelTimer();
        onStateChange(oldState, state);
    }

    public synchronized void stop() {
        if (state == STATE_STOPPED) return;
        int oldState = state;
        state = STATE_STOPPED;
        passedTime = duration;
        cancelTimer();
        // 重置 notesResumeCache
        resetNotesResumeCache();
        onStateChange(oldState, state);
    }

    public synchronized void seek(long ms) {
        if (ms < 0) ms = 0;
        if (ms > duration) ms = duration;
        int oldState = state;
        if (ms != passedTime) {
            passedTime = ms;
            // 通过二分查找调整 cursor
            for (int i = 0; i < cursors.length; ++i) {
                seekTrack(i, ms);
            }
        }
        state = (passedTime == duration) ? STATE_STOPPED : STATE_PAUSED;
        if (state != oldState) {
            cancelTimer();
            onStateChange(oldState, state);
        }
    }

    public int getState() {
        return state;
    }

    public long getCurrentTime() {
        return passedTime;
    }

    public synchronized void addEventListener(EventListener listener) {
        if (listener == null) return;
        eventListeners.add(listener);
    }

    public synchronized void removeEventListener(EventListener listener) {
        if (listener == null) return;
        eventListeners.remove(listener);
    }

    public synchronized void clearEventListeners() {
        eventListeners.clear();
    }


    private synchronized void update(long time) {
        // 判定状态
        if (state != STATE_PLAYING) return;

        long delta = time - lastScheduleTime;
        lastScheduleTime = time;
        passedTime += delta;

        if (passedTime >= duration) {
            passedTime = duration;
        }

        // 调整指针，广播事件
        for (int i = 0; i < cursors.length; ++i) {
            JMidiTrack track = midiFile.getTrack(i);
            List<JMidiEvent> events = new LinkedList<>();
            while (cursors[i] < track.events.size()) {
                JMidiEvent e = track.events.get(cursors[i]);
                if (passedTime < e.ms) {
                    break;
                }
                events.add(e);
                updateNotesResumeCache(i, e);
                cursors[i] += 1;
            }
            if (!events.isEmpty()) {
                onEvents(i, events, false);
            }
        }

        onUpdate(delta);

        if (passedTime == duration) {
            stop();
        }
    }

    private void onStateChange(int oldState, int newState) {
        for (EventListener listener : eventListeners) {
            listener.onStateChange(this, oldState, newState);
        }
    }

    private void onEvents(int tid, List<JMidiEvent> events, boolean isResumedNotes) {
        for (EventListener listener : eventListeners) {
            listener.onEvents(this, tid, events, isResumedNotes);
        }
    }

    private void onUpdate(long deltaMs) {
        for (EventListener listener : eventListeners) {
            listener.onUpdate(this, deltaMs);
        }
    }

    private void cancelTimer() {
        if (timer != null) {
            timer.cancel();
            timer = null;
        }
    }

    private void seekTrack(int tid, long ms) {
        JMidiTrack track = midiFile.getTrack(tid);
        // 如果不需要恢复未弹完的音符，则用二分查找来快速 seek
        if (notesResumeCache == null) {
            int low = 0;
            int high = track.events.size();

            while (low < high) {
                int mid = (low + high) >>> 1;
                if (ms < track.events.get(mid).ms) {
                    high = mid;
                } else {
                    low = mid + 1;
                }
            }
            // 找第一个大于等于 ms 的位置
            int i = low - 1;
            while (i >= 0 && (track.events.get(i).ms == ms)) {
                i -= 1;
            }
            cursors[tid] = i + 1;
            return;
        }

        // 需要恢复 notes 的情况，就使用顺序查找的算法
        resetNotesResumeCache();
        if (ms >= duration) {
            cursors[tid] = track.events.size();
            return;
        }
        for (int i = 0; i < track.events.size(); ++i) {
            JMidiEvent e = track.events.get(i);
            updateNotesResumeCache(tid, e);
            if (e.ms >= ms) {
                cursors[tid] = i;
                return;
            }
        }
    }

    private void resetNotesResumeCache() {
        if (notesResumeCache == null) return;
        Arrays.fill(notesResumeCache, null);
    }

    private void updateNotesResumeCache(int tid, JMidiEvent e) {
        if (notesResumeCache == null) return;
        if (!e.isNote()) return;
        notesResumeCache[tid * PITCH_LIMIT + e.getPitch()] = e.getVelocity() > 0 ? e : null;
    }

    private void resumeNotes() {
        if (notesResumeCache == null) return;
        LinkedList<JMidiEvent> notes = new LinkedList<>();
        JMidiEvent e;
        for (int i = 0; i < cursors.length; ++i) {
            notes.clear();
            for (int j = 0; j < PITCH_LIMIT; ++j) {
                e = notesResumeCache[i * PITCH_LIMIT + j];
                if (e != null) {
                    notes.add(e);
                }
            }
            if (!notes.isEmpty()) {
                onEvents(i, notes, true);
            }
        }
    }
}
